<?php

namespace persistance;

require_once(APP_ROOT . '/modules/log/log.php');
require_once(APP_ROOT . '/modules/log/logCommon.php');
require_once(APP_ROOT . '/modules/common/Errors.php');

use \PDO;
use log\Log;
use log\LogCommon;
use common\Errors;

/**
 * Created by JetBrains PhpStorm.
 * User: dodasso
 * Date: 7/14/13
 * Time: 7:50 PM
 * To change this template use File | Settings | File Templates.
 */

class DBConnector {
    /* @property PDO $db Database PDO. */
    private $db = null;

    /* @var bool $connected Is initialized. */
    private $connected = false;

    private $lastDBErrorCode = null;

    private $lastDBErrorInfo = null;

    public function __construct() {
        Log::LOG_PERSISTANCE_FB();
        Log::LOG_PERSISTANCE_FE();
    }

    public function __destruct() {
        Log::LOG_PERSISTANCE_FB();

        if($this->isConnected()) {
            $this->disconnect();
        }

        Log::LOG_PERSISTANCE_FE();
    }

    /**
     * @return int
     */
    public function connect() {
        Log::LOG_PERSISTANCE_FB();

        if($this->isConnected()) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Already connected to DB.");
            return Errors::INTEGRAL_ERR_PERSISTANCE_ALREADY_CONNECTED;
        }

        $this->lastDBErrorCode = null;
        $this->lastDBErrorInfo = null;

        try {
            $config = json_decode(
                file_get_contents(APP_ROOT . "/modules/config/persistance/config.json", TRUE), TRUE);
            assert((!is_null($config)) && (is_array($config)));
            assert(array_key_exists("database", $config));
            assert(array_key_exists("hostname", $config));
            assert(array_key_exists("password", $config));
            assert(array_key_exists("port", $config));
            assert(array_key_exists("username", $config));
            $this->db = new PDO('mysql:host=' . $config['hostname'] .
                    ';dbname=' . $config['database'],
                $config['username'], $config['password']);
        } catch (PDOException $e) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Exception. " . $e->getMessage());
            $this->lastDBErrorCode = $this->db->errorCode();
            $this->lastDBErrorInfo = $this->db->errorInfo();
            return Errors::INTEGRAL_ERR_PERSISTANCE_CANNOT_CREATE_CONNECTOR;
        }

        $this->connected = true;

        Log::LOG_PERSISTANCE_FE();

        return Errors::INTEGRAL_ERR_NONE;
    }

    /**
     * @return int
     */
    public function disconnect() {
        Log::LOG_PERSISTANCE_FB();

        if(!$this->isConnected()) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Not connected to DB.");
            return Errors::INTEGRAL_ERR_PERSISTANCE_NOT_CONNECTED;
        }

        $this->db              = null;
        $this->connected       = false;
        $this->lastDBErrorCode = null;
        $this->lastDBErrorInfo = null;

        Log::LOG_PERSISTANCE_FE();

        return Errors::INTEGRAL_ERR_NONE;
    }

    /**
     * @return bool
     */
    public function isConnected() {
        Log::LOG_PERSISTANCE_FB();
        Log::LOG_PERSISTANCE_FE();

        return $this->connected;
    }

    public function lastDBErrorCode() {
        Log::LOG_PERSISTANCE_FB();

        if(!$this->isConnected()) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Not connected to DB.");
            return null;
        }

        Log::LOG_PERSISTANCE_FE();

        return $this->lastDBErrorCode;
    }

    public function lastDBErrorInfo() {
        Log::LOG_PERSISTANCE_FB();

        if(!$this->isConnected()) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Not connected to DB.");
            return null;
        }

        Log::LOG_PERSISTANCE_FE();

        return $this->lastDBErrorInfo;
    }

    /**
     * @param string $query
     * @param array $params
     * @return null | array
     */
    public function query($query, $params) {
        Log::LOG_PERSISTANCE_FB();

        $this->lastDBErrorCode = null;
        $this->lastDBErrorInfo = null;

        if((is_null($query)) || (!is_string($query))) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Invalid parameters.");
            return null;
        }

        if((!$this->isConnected()) &&
            (!$this->connect())) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Not connected to DB.");
            return null;
        }

        $st = $this->db->prepare($query);

        if($st === false) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeCritical, "Unable to prepare statement.");
            return null;
        }

        if((!is_null($params)) && (!empty($params))) {
            $bindIdx = 1;
            foreach($params as $value) {
                if(count($value) != 3) {
                    continue;
                }
                if(is_null($value[2])) {
                    $st->bindParam($bindIdx, $value[0], $value[1]);
                } else {
                    $st->bindParam($bindIdx, $value[0], $value[1], $value[2]);
                }
                ++$bindIdx;
            }
        }

        if($st->execute() === false) {
            $this->lastDBErrorCode = $st->errorCode();
            $this->lastDBErrorInfo = $st->errorInfo();
            Log::LOG_PERSISTANCE(LogCommon::logTypeCritical, "Unable execute statement. " .
                implode(' ', $this->lastDBErrorInfo));
            $st->closeCursor();
            return null;
        }

        $rsa = $st->fetchAll();
        if(empty($rsa)) {
            // Nothing was found.
            $st->closeCursor();
            return null;
        }
        $st->closeCursor();

        Log::LOG_PERSISTANCE_FE();

        return $rsa;
    }

    public function insert($query, $params) {
        Log::LOG_PERSISTANCE_FB();

        $this->lastDBErrorCode = null;
        $this->lastDBErrorInfo = null;

        if((is_null($query)) || (!is_string($query)) ||
            (!is_array($params)) || (empty($params))) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Invalid parameters.");
            return null;
        }

        if((!$this->isConnected()) &&
            (!$this->connect())) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Not connected to DB.");
            return null;
        }

        $st = $this->db->prepare($query);

        if($st === false) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeCritical, "Unable to prepare statement.");
            return null;
        }

        $bindIdx = 1;
        foreach($params as $value) {
            if(count($value) != 3) {
                continue;
            }
            if(is_null($value[2])) {
                $st->bindParam($bindIdx, $value[0], $value[1]);
            } else {
                $st->bindParam($bindIdx, $value[0], $value[1], $value[2]);
            }
            ++$bindIdx;
        }

        if($st->execute() === false) {
            $this->lastDBErrorCode = $st->errorCode();
            $this->lastDBErrorInfo = $st->errorInfo();
            Log::LOG_PERSISTANCE(LogCommon::logTypeCritical, "Unable to execute statement. " .
                implode(' ', $this->lastDBErrorInfo));
            $st->closeCursor();
            return null;
        }

        $st->closeCursor();

        Log::LOG_PERSISTANCE_FE();

        return $this->db->lastInsertId();
    }

    public function delete($query, $params) {
        Log::LOG_PERSISTANCE_FB();

        $this->lastDBErrorCode = null;
        $this->lastDBErrorInfo = null;

        if((is_null($query)) || (!is_string($query)) ||
            (!is_array($params)) || (empty($params))) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Invalid parameters.");
            return null;
        }

        if((!$this->isConnected()) &&
            (!$this->connect())) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeError, "Not connected to DB.");
            return null;
        }

        $st = $this->db->prepare($query);

        if($st === false) {
            Log::LOG_PERSISTANCE(LogCommon::logTypeCritical, "Unable to prepare statement.");
            return null;
        }

        $bindIdx = 1;
        foreach($params as $value) {
            if(count($value) != 3) {
                continue;
            }
            if(is_null($value[2])) {
                $st->bindParam($bindIdx, $value[0], $value[1]);
            } else {
                $st->bindParam($bindIdx, $value[0], $value[1], $value[2]);
            }
            ++$bindIdx;
        }

        if($st->execute() === false) {
            $this->lastDBErrorCode = $st->errorCode();
            $this->lastDBErrorInfo = $st->errorInfo();
            Log::LOG_PERSISTANCE(LogCommon::logTypeCritical, "Unable execute statement. " .
                implode(' ', $this->lastDBErrorInfo));
            $st->closeCursor();
            return null;
        }

        $st->closeCursor();

        Log::LOG_PERSISTANCE_FE();

        return $st->rowCount();
    }
}